package com.hanson.aop;

import com.hanson.annotation.RateLimiter;
import com.hanson.annotation.UserLogin;
import com.hanson.common.constant.Constant;
import com.hanson.common.exception.RateLimitException;
import com.hanson.pojo.vo.TbIpList;
import com.hanson.pojo.vo.TbIpListLog;
import com.hanson.pojo.vo.User;
import com.hanson.rate_limit.RateStrategy;
import com.hanson.service.ITbIpListLogService;
import com.hanson.service.ITbIpListService;
import com.hanson.service.ITbIpVisitLogService;
import com.hanson.utils.IpUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;

/**
 * @CLassName RateLimiterAspect
 * @Description 限流处理切面
 * @Author HansonLee
 * @Date 2022/08/30
 **/
@Aspect
@Component
public class RateLimiterAspect {
    private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class);

    @Qualifier(value = "stringTemplate")
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private ITbIpVisitLogService ipVisitLogService;

    @Autowired
    private ITbIpListService ipListService;

    @Autowired
    private ITbIpListLogService ipListLogService;

    @Autowired
    private RedisScript<Long> limitScript;
 
    @Before("@annotation(rateLimiter)")
    @Order(1)
    public void doBefore(JoinPoint point, RateLimiter rateLimiter) {
        int time = rateLimiter.time();
        int count = rateLimiter.count();
 
        String combineKey = getCombineKey(rateLimiter, point);
        List<String> keys = Collections.singletonList(combineKey);
        try {
            Long number = redisTemplate.execute(limitScript, keys, count, time);
            String ip = IpUtils.getEffectiveIp(IpUtils.getIpAddr(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()));
            String ipOffLine = IpUtils.getIpOffLine(ip);
            if (number == null || number.intValue() > count) {
                //添加到访问黑名单
                TbIpList ipList = new TbIpList();
                ipList.setIp(ip).setStatus(Constant.DELETE_FLAG_CLOSE);
                Integer exist = ipListService.selectCount(ipList);
                if (!(exist > 0)){
                    //新增黑名单
                    ipList.setIpAddress(ipOffLine)
                            .setCreateBy(Constant.ADMIN_USER)
                            .setUpdateBy(Constant.ADMIN_USER)
                            .setType(Constant.BLACK_LIST_IP);
                    ipListService.add(ipList);
                }else {
                    //白名单移至黑名单
                    int ud = ipListService.update(ipList);
                    if (ud > 0){
                        TbIpListLog ipListLog = new TbIpListLog();
                        ipListLog.setCreateBy(Constant.ADMIN_USER)
                                .setIp(ip)
                                .setIpAddress(ipOffLine)
                                .setOperation("移至黑名单");
                        ipListLogService.add(ipListLog);
                    }
                }
                throw new RateLimitException("访问过于频繁，请稍候再试");
            }
            log.info("限制访问数'{}',当前访问数'{}',缓存key'{}'", count + "/" + time + "s", number.intValue(), combineKey);
        } catch (RateLimitException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException("服务器限流异常，请稍候再试!" + e);
        }
    }


    public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
        StringBuffer stbf = new StringBuffer(rateLimiter.key());
        //组合ip
        if (rateLimiter.rateStrategy() == RateStrategy.IP) {
            String ipAddr = IpUtils.getEffectiveIp(IpUtils.getIpAddr(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()));
            stbf.append(ipAddr).append("-");
        }

        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        //组合用户名
        if (!method.isAnnotationPresent(UserLogin.class)){
            //登陆接口不组合用户名
            String userName;
            Subject subject = SecurityUtils.getSubject();
            Object principal = subject.getPrincipal();
            if (principal instanceof User) {
                // 获取用户名
                userName = ((User) principal).getUserName();
            }else {
                userName = "DMALL";
            }
            stbf.append(userName).append("-");
        }

        //组合类、方法路径或者url
        Class<?> targetClass = method.getDeclaringClass();
        boolean clazzRequestMapping = targetClass.isAnnotationPresent(RequestMapping.class);
        String clazz;
        String meth;
        if (clazzRequestMapping){
            RequestMapping clazzRm = targetClass.getAnnotation(RequestMapping.class);
            String[] cvalue = clazzRm.value();
            clazz = cvalue[0];
            boolean methodRequestMapping = method.isAnnotationPresent(RequestMapping.class);
            boolean methodPostMapping = method.isAnnotationPresent(PostMapping.class);
            boolean methodGetMapping = method.isAnnotationPresent(GetMapping.class);
            boolean methodPutMapping = method.isAnnotationPresent(PutMapping.class);
            boolean methodDeleteMapping = method.isAnnotationPresent(DeleteMapping.class);
            boolean methodPatchMapping = method.isAnnotationPresent(PatchMapping.class);
            if (methodRequestMapping){
                RequestMapping methodRm = method.getAnnotation(RequestMapping.class);
                String[] value = methodRm.value();
                meth = value[0];
            }else if (methodPostMapping){
                PostMapping methodPom = method.getAnnotation(PostMapping.class);
                String[] value = methodPom.value();
                meth = value[0];
            }else if (methodGetMapping){
                GetMapping methodGm = method.getAnnotation(GetMapping.class);
                String[] value = methodGm.value();
                meth = value[0];
            }else if (methodPutMapping){
                PutMapping methodPum = method.getAnnotation(PutMapping.class);
                String[] value = methodPum.value();
                meth = value[0];
            }else if (methodDeleteMapping){
                DeleteMapping methodDm = method.getAnnotation(DeleteMapping.class);
                String[] value = methodDm.value();
                meth = value[0];
            }else if (methodPatchMapping){
                PatchMapping methodPam = method.getAnnotation(PatchMapping.class);
                String[] value = methodPam.value();
                meth = value[0];
            }else {
                meth = method.getName();
            }
            stbf.append(clazz).append(meth);
        }else {
            clazz = targetClass.getName();
            meth = method.getName();
            stbf.append(clazz).append("-").append(meth);
        }
        return stbf.toString();
    }
}